import java.util.*;

import org.jfree.*;
import org.jfree.ui.RefineryUtilities;

public class GeneticEngine {
	// Data members
	private List<Chromosome> _population = null; // Chromosome pool
	private Chromosome _best; // Best chromosome in experiment
	private GeneticUtils _utils; // Genetic utilitiles library
	
	private List<Double> bestValues = null;
	private List<Double> avgValues = null;
	
	public GeneticEngine(int length, InputData input, int numColors) {

		_utils = new GeneticUtils(length, input, numColors);

		_population = new LinkedList<Chromosome>();

		bestValues = new ArrayList<Double>(_utils.GENERATIONS);
		avgValues = new ArrayList<Double>(_utils.GENERATIONS);
		
		// create new population
		for (int i = 0; i < GeneticUtils.POPULATION; ++i) {
			Chromosome c = new Chromosome(length);
			
			// calculate fitness
			c.setFitness(_utils.fitness1(c));
			_population.add(c);
		}
		
		_best = _population.get(0);
	
	}


	public List<List<Double>> start() {
		// start breeding chromosoms
		int i = 0;
		
		int valBest = 0;
		boolean stopRunning = false;
		while (i < GeneticUtils.GENERATIONS && !stopRunning) {
			List<Chromosome> nextGeneration = createNextGeneration();
			
			if (i % 200 == 0)
				System.out.println("Current generation is: " + i);
			
			_population = nextGeneration;
			
			i++;
			
			List<Chromosome> sortedPopulation = new LinkedList<Chromosome>(_population);
			Collections.sort(sortedPopulation);
			_best = sortedPopulation.get(0);
			
			if (_utils.getMaxError() - _best.getFitness() == 0)
				stopRunning = true;
			
			valBest = (int)	_best.getFitness();
			
			int count4Color = 0;
			List<Integer> coloring = _best.getAll();
			for (int j = 0; j < coloring.size(); ++j) {
				int currColor = coloring.get(j);
				if (currColor == 3)
					++count4Color;
			}
			
			// calculate average fitness
			double sum = 0;
			for (Chromosome c : _population)
				sum += c.getFitness();
			
			avgValues.add(sum / _utils.POPULATION);
			bestValues.add(_best.getFitness());
		}
		
		List<List<Double>> retLists = new ArrayList<List<Double>>();
		retLists.add(bestValues);
		retLists.add(avgValues);
		
		return retLists;
	}

	/**
	 * Selects parents according to their fitness and probability
	 * 
	 * @param totalMass
	 *            total mass of population fitness grades
	 * @return selected parents to be breed
	 */
	private List<Chromosome> selectParents(double totalMass) {

		double rnd1 = GeneticUtils.rnd.nextDouble() * totalMass;
		double rnd2 = GeneticUtils.rnd.nextDouble() * totalMass;

		if (rnd1 > rnd2) {
			double temp = rnd1;
			rnd1 = rnd2;
			rnd2 = temp;
		}

		double[] parentsVals = { rnd1, rnd2 };
		List<Chromosome> retParents = new LinkedList<Chromosome>();

		// Find parents according to their fitness
		double distance = 0;
		for (Chromosome c : _population) {
			distance += c.getFitness(); // Accumulate fitness

			// Check if select current chromosome according to its fitness
			if (parentsVals[retParents.size()] <= distance) {
				retParents.add(c); // Current chromosome was selected

				if (retParents.size() == 2)
					break; // If 2 parents are chosen - break loop

				// If both numbers points on same parent
				if (parentsVals[retParents.size()] <= distance) {
					retParents.add(c);
				}
			}

			if (retParents.size() == 2)
				break; // If 2 parents are chosen - break loop
		}

		return retParents; // Return the parents list
	}

	/**
	 * Creates the next generation according to the genes of current chromosomes
	 * 
	 * @return new pool of chromosomes
	 */
	private List<Chromosome> createNextGeneration() {
		List<Chromosome> nextGeneration = new LinkedList<Chromosome>();
				
		// calculate the total fitness mass of current population
		double totalMass = calcTotalFitnessMass(_population);
		List<Chromosome> parents = null;
		
		// copy best chromosome to next generation (elitisim)
		List<Chromosome> sortedPopulation = new LinkedList<Chromosome>(_population);
		Collections.sort(sortedPopulation);
		
		for (int i = 0; i < GeneticUtils.ELITISM; ++i)
			nextGeneration.add(sortedPopulation.get(i).clone());
		

				
		// Choose parents and breed them
		while (nextGeneration.size()<_population.size()) {
			parents = selectParents(totalMass);

			List<Chromosome> offsprings=null;
				nextGeneration.addAll(_utils.breed(parents.get(0), parents.get(1)));
		}

		return nextGeneration;
	}

	/**
	 * Calculate the total mass of all chromosomes' fitness
	 * @param population chromosome pool 
	 * @return accumulative fitness mass of population
	 */
	private double calcTotalFitnessMass(List<Chromosome> population) {
		double sum = 0;
		for (Chromosome c : population)
			sum += c.getFitness();

		return sum;
	}
	
	/**
	 * Gets best chromosome
	 * @return best chromosome
	 */
	public Chromosome getBest() {
		return _best;
	}
}